package com.sz.admin.system.service.impl;

import com.sz.admin.system.pojo.po.SysFileMonitor;
import com.sz.admin.system.service.FolderMonitorService;
import com.sz.admin.system.service.SysFileMonitorService;
import com.sz.admin.system.service.SysFileService;
import com.sz.admin.system.service.SysMonitorFileRecordService;
import com.sz.admin.system.service.FileMonitorNotificationService;
import com.sz.admin.system.service.OcrRecognitionService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;
import org.springframework.web.multipart.MultipartFile;
import com.sz.admin.system.util.CustomMultipartFile;

import java.io.*;
import java.nio.file.*;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
 * 文件夹监控服务实现类
 *
 * @author sz-admin
 * @since 2025-01-13
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class FolderMonitorServiceImpl implements FolderMonitorService {

    private final SysFileMonitorService sysFileMonitorService;
    private final SysMonitorFileRecordService recordService;
    private final SysFileService sysFileService;
    private final FileMonitorNotificationService notificationService;
    private final OcrRecognitionService ocrRecognitionService;

    @Value("${file.upload.path:./uploads}")
    private String uploadBasePath;

    @Value("${server.servlet.context-path:}")
    private String contextPath;

    @Value("${server.port:8080}")
    private String serverPort;

    private final Map<Long, WatchService> watchServices = new ConcurrentHashMap<>();
    private final Map<Long, ScheduledExecutorService> executorServices = new ConcurrentHashMap<>();
    private final Map<Long, ScheduledExecutorService> processingExecutors = new ConcurrentHashMap<>();
    // 用于防止重复处理同一文件的缓存：monitorId -> Set<文件路径_时间戳>
    private final Map<Long, Set<String>> processingCache = new ConcurrentHashMap<>();
    // 映射WatchKey到目录路径，用于处理子目录事件
    private final Map<WatchKey, Path> watchKeyToPath = new ConcurrentHashMap<>();
    private final ScheduledExecutorService mainExecutor = Executors.newScheduledThreadPool(2);

    @Override
    public void startAllMonitors() {
        log.info("启动所有文件监控任务");
        List<SysFileMonitor> enabledMonitors = sysFileMonitorService.getEnabledMonitors();
        
        if (enabledMonitors.isEmpty()) {
            log.info("没有尝试启动的监控任务");
            return;
        }
        
        log.info("开始启动 {} 个监控任务", enabledMonitors.size());
        
        for (SysFileMonitor monitor : enabledMonitors) {
            try {
                startMonitor(monitor.getId());
                // 发送监控启动通知
                notificationService.notifyMonitorStatusChange(monitor.getId(), "STARTED", 
                    "监控任务已启动: " + monitor.getMonitorPath());
            } catch (Exception e) {
                log.error("启动监控任务失败: monitorId={}, error={}", monitor.getId(), e.getMessage(), e);
                // 发送监控启动失败通知
                notificationService.notifyMonitorStatusChange(monitor.getId(), "START_FAILED", 
                    "监控任务启动失败: " + e.getMessage());
            }
        }
        
        log.info("所有监控任务启动完成");
    }

    @Override
    public void stopAllMonitors() {
        log.info("停止所有文件监控任务");
        
        // 获取所有正在运行的监控ID的副本，避免并发修改
        Set<Long> monitorIds = new HashSet<>(watchServices.keySet());
        
        if (monitorIds.isEmpty()) {
            log.info("没有正在运行的监控任务");
            return;
        }
        
        log.info("开始停止 {} 个监控任务", monitorIds.size());
        
        for (Long monitorId : monitorIds) {
            try {
                stopMonitor(monitorId);
            } catch (Exception e) {
                log.error("停止监控任务失败: monitorId={}, error={}", monitorId, e.getMessage(), e);
            }
        }
        
        log.info("所有监控任务已停止");
    }

    @Override
    public void startMonitor(Long monitorId) {
        SysFileMonitor monitor = sysFileMonitorService.getById(monitorId);
        if (monitor == null || !"1".equals(monitor.getStatus())) {
            log.warn("监控配置不存在或未启用: monitorId={}", monitorId);
            return;
        }

        // 如果已经在监控，先停止
        if (watchServices.containsKey(monitorId)) {
            stopMonitor(monitorId);
        }

        try {
            Path monitorPath = Paths.get(monitor.getMonitorPath());
            if (!Files.exists(monitorPath)) {
                log.warn("监控路径不存在: {}", monitor.getMonitorPath());
                return;
            }

            WatchService watchService = FileSystems.getDefault().newWatchService();
            
            // 递归注册所有子目录
            registerDirectoryRecursively(monitorPath, watchService);

            watchServices.put(monitorId, watchService);

            // 创建专用线程池用于监控
            ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(r -> {
                Thread t = new Thread(r, "FileMonitor-" + monitorId);
                t.setDaemon(true);
                return t;
            });
            executorServices.put(monitorId, executor);
            
            // 创建专用线程池用于文件处理
            ScheduledExecutorService processingExecutor = Executors.newScheduledThreadPool(2, r -> {
                Thread t = new Thread(r, "FileProcessor-" + monitorId);
                t.setDaemon(true);
                return t;
            });
            processingExecutors.put(monitorId, processingExecutor);

            // 启动监控线程
            executor.submit(() -> {
                log.info("开始监控文件夹: {} (monitorId={}), 已注册{}个目录", 
                        monitor.getMonitorPath(), monitorId, watchKeyToPath.size());
                
                while (!shouldStopMonitoring(monitorId)) {
                    try {
                        WatchKey key = watchService.poll(1, TimeUnit.SECONDS);
                        if (key != null) {
                            // 获取触发事件的目录路径
                            Path watchedDir = watchKeyToPath.get(key);
                            if (watchedDir == null) {
                                log.warn("无法找到WatchKey对应的目录路径，跳过事件处理: monitorId={}", monitorId);
                                key.reset();
                                continue;
                            }
                            
                            List<WatchEvent<?>> events = key.pollEvents();
                            log.debug("检测到文件系统事件: monitorId={}, 目录: {}, 事件数量: {}", 
                                    monitorId, watchedDir, events.size());
                            
                            for (WatchEvent<?> event : events) {
                                if (event.kind() == StandardWatchEventKinds.OVERFLOW) {
                                    log.warn("文件系统事件溢出: monitorId={}", monitorId);
                                    continue;
                                }

                                @SuppressWarnings("unchecked")
                                WatchEvent<Path> ev = (WatchEvent<Path>) event;
                                Path filename = ev.context();
                                // 正确解析子目录中的文件路径
                                Path fullPath = watchedDir.resolve(filename);
                                
                                log.info("文件事件: {} - {} (monitorId={}, 触发目录: {})", 
                                    event.kind().name(), fullPath, monitorId, watchedDir);

                                if (Files.isRegularFile(fullPath)) {
                                    // 防止重复处理同一文件 - 先检查缓存再记录日志
                                    String fileKey = fullPath.toString();
                                    Set<String> cache = processingCache.computeIfAbsent(monitorId, k -> ConcurrentHashMap.newKeySet());
                                    
                                    if (cache.contains(fileKey)) {
                                        log.debug("文件已在处理队列中，跳过重复事件: {} (monitorId={})", fullPath, monitorId);
                                        continue;
                                    }
                                    
                                    // 只有第一次检测到文件时才记录
                                    log.info("检测到新文件: {} (monitorId={})", fullPath, monitorId);
                                    
                                    // 添加到缓存中防止重复处理
                                    cache.add(fileKey);
                                    
                                    // 使用专用的处理线程池进行延迟处理，确保文件完全写入
                                    ScheduledExecutorService fileProcessor = processingExecutors.get(monitorId);
                                    if (fileProcessor != null && !fileProcessor.isShutdown()) {
                                        fileProcessor.schedule(() -> {
                                            try {
                                                log.info("开始处理文件: {} (monitorId={})", fullPath, monitorId);
                                                processFileWithPath(monitorId, fullPath.toFile(), monitorPath);
                                                log.info("文件处理完成: {} (monitorId={})", fullPath, monitorId);
                                            } catch (Exception e) {
                                                log.error("处理文件失败: {}, error={}", fullPath, e.getMessage(), e);
                                            } finally {
                                                // 处理完成后从缓存中移除
                                                cache.remove(fileKey);
                                            }
                                        }, 2, TimeUnit.SECONDS);
                                    } else {
                                        // 如果线程池不可用，也要从缓存中移除
                                        cache.remove(fileKey);
                                        log.warn("处理线程池不可用，跳过文件处理: {} (monitorId={})", fullPath, monitorId);
                                    }
                                } else if (Files.isDirectory(fullPath) && event.kind() == StandardWatchEventKinds.ENTRY_CREATE) {
                                    // 如果是新创建的目录，也需要注册监控
                                    try {
                                        log.info("检测到新目录，注册监控: {} (monitorId={})", fullPath, monitorId);
                                        registerSingleDirectory(fullPath, watchService);
                                        
                                        // 处理新目录中的现有文件
                                        SysFileMonitor currentMonitor = sysFileMonitorService.getById(monitorId);
                                        if (currentMonitor != null) {
                                            Path currentBasePath = Paths.get(currentMonitor.getMonitorPath());
                                            processExistingFilesRecursively(monitorId, fullPath.toFile(), currentBasePath);
                                        }
                                    } catch (IOException ioException) {
                                        log.error("注册新目录监控失败: {}, error={}", fullPath, ioException.getMessage(), ioException);
                                    }
                                } else {
                                    log.debug("跳过非文件对象: {} (monitorId={})", fullPath, monitorId);
                                }
                            }
                            key.reset();
                        } else {
                            // 每10秒输出一次心跳日志，证明监控线程仍在运行
                            if (System.currentTimeMillis() % 10000 < 1000) {
                                log.debug("监控线程心跳: monitorId={}, path={}", monitorId, monitor.getMonitorPath());
                            }
                        }
                    } catch (InterruptedException e) {
                        // 线程被中断，通常是应用程序关闭或监控停止
                        log.info("监控线程被中断，停止监控: monitorId={}", monitorId);
                        Thread.currentThread().interrupt();
                        break;
                    } catch (Exception e) {
                        log.error("监控线程异常: monitorId={}, error={}", monitorId, e.getMessage(), e);
                        // 发送错误通知
                        try {
                            notificationService.notifyMonitorStatusChange(monitorId, "ERROR", 
                                "监控线程异常: " + e.getMessage());
                        } catch (Exception ne) {
                            log.error("发送监控异常通知失败", ne);
                        }
                        
                        if (!Thread.currentThread().isInterrupted()) {
                            try {
                                Thread.sleep(5000); // 等待5秒后重试
                            } catch (InterruptedException ie) {
                                log.info("监控线程被中断，停止监控: monitorId={}", monitorId);
                                Thread.currentThread().interrupt();
                                break;
                            }
                        }
                    }
                }
                
                log.info("监控线程结束: monitorId={}", monitorId);
            });

            // 处理现有文件
            processExistingFiles(monitorId, monitorPath.toFile());

            log.info("成功启动监控: monitorId={}, path={}", monitorId, monitor.getMonitorPath());

        } catch (Exception e) {
            log.error("启动监控失败: monitorId={}, error={}", monitorId, e.getMessage(), e);
        }
    }

    @Override
    public void stopMonitor(Long monitorId) {
        log.info("停止监控: monitorId={}", monitorId);

        // 停止监控线程池
        ScheduledExecutorService executor = executorServices.remove(monitorId);
        if (executor != null) {
            try {
                // 先尝试优雅关闭
                executor.shutdown();
                if (!executor.awaitTermination(3, TimeUnit.SECONDS)) {
                    // 如果3秒内未关闭，则强制关闭
                    executor.shutdownNow();
                    if (!executor.awaitTermination(2, TimeUnit.SECONDS)) {
                        log.warn("监控线程池无法完全关闭: monitorId={}", monitorId);
                    }
                }
                log.debug("监控线程池已关闭: monitorId={}", monitorId);
            } catch (InterruptedException e) {
                // 当前线程被中断，强制关闭线程池
                executor.shutdownNow();
                Thread.currentThread().interrupt();
                log.info("监控线程池关闭被中断，强制关闭: monitorId={}", monitorId);
            }
        }
        
        // 停止处理线程池
        ScheduledExecutorService processingExecutor = processingExecutors.remove(monitorId);
        if (processingExecutor != null) {
            try {
                // 先尝试优雅关闭
                processingExecutor.shutdown();
                if (!processingExecutor.awaitTermination(3, TimeUnit.SECONDS)) {
                    // 如果3秒内未关闭，则强制关闭
                    processingExecutor.shutdownNow();
                    if (!processingExecutor.awaitTermination(2, TimeUnit.SECONDS)) {
                        log.warn("处理线程池无法完全关闭: monitorId={}", monitorId);
                    }
                }
                log.debug("处理线程池已关闭: monitorId={}", monitorId);
            } catch (InterruptedException e) {
                // 当前线程被中断，强制关闭线程池
                processingExecutor.shutdownNow();
                Thread.currentThread().interrupt();
                log.info("处理线程池关闭被中断，强制关闭: monitorId={}", monitorId);
            }
        }

        // 关闭监控服务
        WatchService watchService = watchServices.remove(monitorId);
        if (watchService != null) {
            try {
                // 清理属于当前监控的WatchKey映射
                Set<WatchKey> keysToRemove = new HashSet<>();
                for (Map.Entry<WatchKey, Path> entry : watchKeyToPath.entrySet()) {
                    try {
                        // 取消WatchKey并标记为需要移除
                        entry.getKey().cancel();
                        keysToRemove.add(entry.getKey());
                    } catch (Exception e) {
                        log.debug("清理WatchKey时发生异常: {}", e.getMessage());
                        keysToRemove.add(entry.getKey()); // 异常的key也要移除
                    }
                }
                
                // 移除所有标记的WatchKey
                for (WatchKey key : keysToRemove) {
                    watchKeyToPath.remove(key);
                }
                
                watchService.close();
                log.debug("监控服务已关闭: monitorId={}, 清理了{}\u4e2aWatchKey", monitorId, keysToRemove.size());
            } catch (IOException e) {
                log.error("关闭监控服务失败: monitorId={}, error={}", monitorId, e.getMessage(), e);
            }
        }
        // 清理处理缓存
        processingCache.remove(monitorId);
        
        log.info("监控已停止: monitorId={}", monitorId);
    }

    @Override
    public void processFile(Long monitorId, File file) {
        processFileWithTag(monitorId, file, null);
    }
    
    /**
     * 处理文件（带文件标签支持）
     */
    public void processFileWithTag(Long monitorId, File file, Path basePath) {
        log.info("开始处理文件: {} (monitorId={}, 文件大小: {})", file.getAbsolutePath(), monitorId, file.length());
        
        SysFileMonitor monitor = sysFileMonitorService.getById(monitorId);
        if (monitor == null) {
            log.error("未找到监控配置: monitorId={}", monitorId);
            return;
        }
        
        log.info("找到监控配置: monitorId={}, 允许的文件类型: {}", monitorId, monitor.getFileTypes());
        
        // 提取文件标签（子目录名）
        String fileTag = null;
        if (basePath != null) {
            fileTag = extractFileTag(file.toPath(), basePath);
            log.info("提取文件标签: {} -> {}", file.getAbsolutePath(), fileTag);
        }

        String errorMessage = null;
        boolean success = false;
        String newFilename = null;
        String storagePath = null;
        String objectName = null;
        String fileUrl = null;
        String fileMd5 = null;
        Long sfId = null; // 关联的sys_file表ID
        String carNumber = null; // OCR识别出的车牌号
        String carNumberRes = null; // OCR识别结果JSON

        try {
            // 检查文件类型
            List<String> allowedTypes = parseFileTypes(monitor.getFileTypes());
            if (!isImageFile(file, allowedTypes)) {
                log.debug("文件类型不匹配，跳过: {}", file.getName());
                return;
            }

            // 检查文件大小
            if (monitor.getMaxFileSize() != null && file.length() > monitor.getMaxFileSize()) {
                errorMessage = "文件大小超过限制: " + file.length() + " > " + monitor.getMaxFileSize();
                log.warn("文件大小超过限制，跳过: {} ({}字节)", file.getName(), file.length());
                return;
            }

            // 计算文件MD5，用于重复检测
            fileMd5 = getFileMD5(file);
            if (fileMd5 == null) {
                errorMessage = "计算文件MD5失败";
                log.error("计算文件MD5失败，跳过处理: {}", file.getName());
                return;
            }
            
            // 检查文件是否已存在（基于MD5）
            if (isFileAlreadyExists(fileMd5)) {
                log.info("文件已存在（MD5: {}），跳过重复处理: {}", fileMd5, file.getName());
                
                // 发送重复文件跳过通知
                notificationService.notifyDuplicateImageSkipped(file.getName(), fileMd5);
                
                // 删除源文件（如果配置了）
                if ("1".equals(monitor.getDeleteSource())) {
                    if (file.delete()) {
                        log.info("已删除重复源文件: {}", file.getAbsolutePath());
                    } else {
                        log.warn("删除重复源文件失败: {}", file.getAbsolutePath());
                    }
                }
                // 对于重复文件，直接返回，不记录到 sys_monitor_file_record 表
                return;
            }

            // 生成存储路径
            storagePath = generateStoragePath(monitor.getStorageDir());
            
            // 生成新文件名
            newFilename = generateNewFilename(file.getName());

            // 本地文件存储
            String localFilePath = saveFileToLocal(file, storagePath, newFilename);
            
            // 生成访问URL
            fileUrl = generateFileUrl(storagePath, newFilename);
            objectName = storagePath + "/" + newFilename;
            
            // 创建本地文件记录对象
            LocalFileRecord localFileRecord = new LocalFileRecord(
                localFilePath, newFilename, file.length(), 
                getContentType(file.getName()), objectName, fileUrl, fileMd5
            );
            
            // 记录到文件表
            sfId = logLocalFile(localFileRecord);
            
            success = true;

            log.info("文件处理成功: {} -> {} (MD5: {}, sfId: {})", file.getName(), fileUrl, fileMd5, sfId);
            
            // 调用OCR识别服务
            try {
                log.info("开始OCR识别: {}", file.getName());
                OcrRecognitionService.OcrResult ocrResult = ocrRecognitionService.recognizeText(file);
                
                if (ocrResult.isSuccess()) {
                    carNumber = ocrResult.getTotalText();
                    carNumberRes = ocrResult.getFullResponse();
                    log.info("OCR识别成功: {}, 车牌号: {}", file.getName(), carNumber);
                } else {
                    log.warn("OCR识别失败: {}, 错误信息: {}", file.getName(), ocrResult.getErrorMessage());
                    // OCR失败不影响文件处理结果
                }
            } catch (Exception ocrEx) {
                log.error("OCR识别异常: {}, error: {}", file.getName(), ocrEx.getMessage(), ocrEx);
                // OCR异常不影响文件处理结果
            }

            // 删除源文件（如果配置了）
            if ("1".equals(monitor.getDeleteSource())) {
                if (file.delete()) {
                    log.info("已删除源文件: {}", file.getAbsolutePath());
                } else {
                    log.warn("删除源文件失败: {}", file.getAbsolutePath());
                }
            }

        } catch (Exception e) {
            errorMessage = e.getMessage();
            log.error("处理文件失败: {}, error={}", file.getName(), e.getMessage(), e);
            // 发送文件处理错误通知
            notificationService.notifyFileProcessError(file.getName(), errorMessage);
        }
        
        // 记录处理结果（包含OCR识别结果）
        recordService.recordFileProcessWithOCR(monitorId, file, newFilename, storagePath, 
            objectName, fileUrl, success, errorMessage, fileMd5, fileTag, sfId, carNumber, carNumberRes);
            
        // 如果成功处理了新文件，发送实时通知
        if (success && errorMessage == null) {
            // 构建文件记录对象用于通知
            com.sz.admin.system.pojo.po.SysMonitorFileRecord notificationRecord = new com.sz.admin.system.pojo.po.SysMonitorFileRecord();
            notificationRecord.setMonitorId(monitorId);
            notificationRecord.setOriginalFilename(file.getName());
            notificationRecord.setNewFilename(newFilename);
            notificationRecord.setFileUrl(fileUrl);
            notificationRecord.setFileSize(file.length());
            notificationRecord.setFileType(getFileExtension(file.getName()));
            notificationRecord.setFileMd5(fileMd5);
            notificationRecord.setProcessTime(java.time.LocalDateTime.now());
            notificationRecord.setStoragePath(storagePath);
            
            notificationService.notifyNewImageDetected(notificationRecord);
        }
    }

    @Override
    public boolean isImageFile(File file, List<String> allowedTypes) {
        if (allowedTypes == null || allowedTypes.isEmpty()) {
            // 默认支持的图片类型
            allowedTypes = Arrays.asList("jpg", "jpeg", "png", "gif", "bmp", "webp");
        }

        String filename = file.getName().toLowerCase();
        for (String type : allowedTypes) {
            if (filename.endsWith("." + type.toLowerCase())) {
                return true;
            }
        }
        return false;
    }

    @Override
    public String getFileMD5(File file) {
        try (FileInputStream fis = new FileInputStream(file)) {
            return DigestUtils.md5DigestAsHex(fis);
        } catch (IOException e) {
            log.error("计算文件MD5失败: {}", e.getMessage(), e);
            return null;
        }
    }
    
    /**
     * 检查文件是否已存在（基于MD5哈希）
     * 从 sys_file 表的 eTag 字段和 sys_monitor_file_record 表的 file_md5 字段查找
     */
    private boolean isFileAlreadyExists(String md5Hash) {
        if (md5Hash == null || md5Hash.trim().isEmpty()) {
            return false;
        }
        
        try {
            // 检查 sys_file 表中的 eTag 字段
            com.mybatisflex.core.query.QueryWrapper sysFileQuery = com.mybatisflex.core.query.QueryWrapper.create()
                    .eq(com.sz.admin.system.pojo.po.SysFile::getETag, md5Hash)
                    .limit(1);
            long sysFileCount = sysFileService.count(sysFileQuery);
            
            if (sysFileCount > 0) {
                log.debug("在 sys_file 表中找到重复文件，MD5: {}", md5Hash);
                return true;
            }
            
            // 检查 sys_monitor_file_record 表中的 file_md5 字段（仅检查成功处理的记录）
            com.mybatisflex.core.query.QueryWrapper recordQuery = com.mybatisflex.core.query.QueryWrapper.create()
                    .eq(com.sz.admin.system.pojo.po.SysMonitorFileRecord::getFileMd5, md5Hash)
                    .eq(com.sz.admin.system.pojo.po.SysMonitorFileRecord::getProcessStatus, "1") // 仅检查成功处理的记录
                    .eq(com.sz.admin.system.pojo.po.SysMonitorFileRecord::getDelFlag, "F")
                    .limit(1);
            long recordCount = recordService.count(recordQuery);
            
            if (recordCount > 0) {
                log.debug("在 sys_monitor_file_record 表中找到重复文件，MD5: {}", md5Hash);
                return true;
            }
            
            return false;
            
        } catch (Exception e) {
            log.error("检查文件重复失败，MD5: {}, error: {}", md5Hash, e.getMessage(), e);
            return false; // 发生异常时，不阻止文件处理
        }
    }

    @Override
    public String generateStoragePath(String baseDir) {
        LocalDate now = LocalDate.now();
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd");
        return baseDir + "/" + now.format(formatter);
    }

    /**
     * 递归处理现有文件（带重复检测优化）
     */
    private void processExistingFiles(Long monitorId, File directory) {
        if (!directory.isDirectory()) {
            return;
        }
        
        SysFileMonitor monitor = sysFileMonitorService.getById(monitorId);
        if (monitor == null) {
            return;
        }
        
        Path basePath = Paths.get(monitor.getMonitorPath());
        processExistingFilesRecursively(monitorId, directory, basePath);
    }
    
    /**
     * 递归处理现有文件
     */
    private void processExistingFilesRecursively(Long monitorId, File directory, Path basePath) {
        if (!directory.isDirectory()) {
            return;
        }

        File[] files = directory.listFiles();
        if (files == null) {
            return;
        }

        int totalFiles = files.length;
        int processedFiles = 0;
        int skippedFiles = 0;
        
        log.info("开始处理现有文件，目录: {}, 文件数量: {}", directory.getAbsolutePath(), totalFiles);

        for (File file : files) {
            if (file.isFile()) {
                try {
                    // 快速检查文件类型，避免不必要的MD5计算
                    SysFileMonitor monitor = sysFileMonitorService.getById(monitorId);
                    if (monitor != null) {
                        List<String> allowedTypes = parseFileTypes(monitor.getFileTypes());
                        if (!isImageFile(file, allowedTypes)) {
                            skippedFiles++;
                            continue;
                        }
                    }
                    
                    processFileWithTag(monitorId, file, basePath);
                    processedFiles++;
                } catch (Exception e) {
                    log.error("处理现有文件失败: {}, error={}", file.getName(), e.getMessage(), e);
                }
            } else if (file.isDirectory()) {
                // 递归处理子目录
                processExistingFilesRecursively(monitorId, file, basePath);
            }
        }
        
        log.info("现有文件处理完成，目录: {}, 总数: {}, 处理: {}, 跳过: {}", 
                directory.getAbsolutePath(), totalFiles, processedFiles, skippedFiles);
    }

    /**
     * 检查文件是否存在再处理（带路径信息）
     */
    private void processFileIfExists(Long monitorId, File file) {
        log.info("检查文件是否存在: {} (monitorId={})", file.getAbsolutePath(), monitorId);
        
        if (!file.exists()) {
            log.warn("文件不存在，跳过处理: {} (monitorId={})", file.getAbsolutePath(), monitorId);
            return;
        }
        
        if (!file.isFile()) {
            log.warn("路径不是文件，跳过处理: {} (monitorId={})", file.getAbsolutePath(), monitorId);
            return;
        }
        
        log.info("文件存在且有效，开始处理: {} (monitorId={})", file.getAbsolutePath(), monitorId);
        processFile(monitorId, file);
        log.info("文件处理调用完成: {} (monitorId={})", file.getAbsolutePath(), monitorId);
    }
    
    /**
     * 检查文件是否存在再处理（带基础路径信息用于提取文件标签）
     */
    private void processFileWithPath(Long monitorId, File file, Path basePath) {
        log.info("检查文件是否存在: {} (monitorId={})", file.getAbsolutePath(), monitorId);
        
        if (!file.exists()) {
            log.warn("文件不存在，跳过处理: {} (monitorId={})", file.getAbsolutePath(), monitorId);
            return;
        }
        
        if (!file.isFile()) {
            log.warn("路径不是文件，跳过处理: {} (monitorId={})", file.getAbsolutePath(), monitorId);
            return;
        }
        
        log.info("文件存在且有效，开始处理: {} (monitorId={})", file.getAbsolutePath(), monitorId);
        processFileWithTag(monitorId, file, basePath);
        log.info("文件处理调用完成: {} (monitorId={})", file.getAbsolutePath(), monitorId);
    }
    
    /**
     * 单独注册一个新目录进行监控（用于动态创建的目录）
     */
    private void registerSingleDirectory(Path directory, WatchService watchService) throws IOException {
        WatchKey watchKey = directory.register(watchService, 
            StandardWatchEventKinds.ENTRY_CREATE,
            StandardWatchEventKinds.ENTRY_MODIFY,
            StandardWatchEventKinds.ENTRY_DELETE);
        
        // 将WatchKey与目录路径进行映射
        watchKeyToPath.put(watchKey, directory);
        
        log.debug("已注册单个目录监控: {}", directory);
    }

    /**
     * 递归注册目录和其所有子目录进行监控
     */
    private void registerDirectoryRecursively(Path directory, WatchService watchService) throws IOException {
        // 注册当前目录
        WatchKey watchKey = directory.register(watchService, 
            StandardWatchEventKinds.ENTRY_CREATE,
            StandardWatchEventKinds.ENTRY_MODIFY,
            StandardWatchEventKinds.ENTRY_DELETE);
        
        // 将WatchKey与目录路径进行映射
        watchKeyToPath.put(watchKey, directory);
        
        log.debug("已注册监控目录: {}", directory);
        
        // 递归注册所有子目录
        try (DirectoryStream<Path> stream = Files.newDirectoryStream(directory)) {
            for (Path entry : stream) {
                if (Files.isDirectory(entry)) {
                    registerDirectoryRecursively(entry, watchService);
                }
            }
        }
    }

    /**
     * 从文件路径提取文件标签（子目录名）
     */
    private String extractFileTag(Path filePath, Path basePath) {
        try {
            Path relativePath = basePath.relativize(filePath.getParent());
            if (relativePath.toString().isEmpty() || relativePath.toString().equals(".")) {
                return null; // 直接在根目录下的文件，没有标签
            }
            // 只取第一层子目录名作为标签
            return relativePath.getName(0).toString();
        } catch (Exception e) {
            log.warn("提取文件标签失败: {}, error: {}", filePath, e.getMessage());
            return null;
        }
    }
    private boolean shouldStopMonitoring(Long monitorId) {
        return Thread.currentThread().isInterrupted() || !watchServices.containsKey(monitorId);
    }
    
    /**
     * 获取文件扩展名
     */
    private String getFileExtension(String filename) {
        if (filename == null || filename.isEmpty()) {
            return "";
        }
        int lastDotIndex = filename.lastIndexOf('.');
        return lastDotIndex > 0 ? filename.substring(lastDotIndex + 1).toLowerCase() : "";
    }

    /**
     * 解析文件类型字符串
     */
    private List<String> parseFileTypes(String fileTypes) {
        if (fileTypes == null || fileTypes.trim().isEmpty()) {
            return null;
        }
        
        String[] types = fileTypes.split(",");
        List<String> result = new ArrayList<>();
        for (String type : types) {
            result.add(type.trim().toLowerCase());
        }
        return result;
    }

    /**
     * 生成新文件名
     */
    private String generateNewFilename(String originalFilename) {
        String ext = "";
        int lastDotIndex = originalFilename.lastIndexOf('.');
        if (lastDotIndex > 0) {
            ext = originalFilename.substring(lastDotIndex);
        }
        
        // 使用时间戳生成唯一文件名
        long timestamp = System.currentTimeMillis();
        return "img_" + timestamp + ext;
    }

    /**
     * 将File转换为MultipartFile
     */
    private MultipartFile convertToMultipartFile(File file) throws IOException {
        return new CustomMultipartFile(file);
    }

    /**
     * 保存文件到本地
     */
    private String saveFileToLocal(File sourceFile, String storagePath, String newFilename) throws IOException {
        // 创建目标目录
        String fullStoragePath = uploadBasePath + "/" + storagePath;
        Path targetDir = Paths.get(fullStoragePath);
        Files.createDirectories(targetDir);
        
        // 目标文件路径
        Path targetFilePath = targetDir.resolve(newFilename);
        
        // 复制文件
        Files.copy(sourceFile.toPath(), targetFilePath, StandardCopyOption.REPLACE_EXISTING);
        
        log.debug("文件已保存到: {}", targetFilePath.toAbsolutePath());
        return targetFilePath.toAbsolutePath().toString();
    }

    /**
     * 生成文件访问URL
     */
    private String generateFileUrl(String storagePath, String filename) {
        //String baseUrl = "http://localhost:" + serverPort;
        String baseUrl = "";
        if (contextPath != null && !contextPath.isEmpty()) {
            contextPath = "/admin";
            baseUrl += contextPath;
        }
        return baseUrl + "/uploads/" + storagePath + "/" + filename;
    }

    /**
     * 记录本地文件信息到数据库
     */
    private Long logLocalFile(LocalFileRecord record) {
        try {
            // 创建 SysFile 对象
            com.sz.admin.system.pojo.po.SysFile sysFile = new com.sz.admin.system.pojo.po.SysFile();
            sysFile.setFilename(record.getFilename());
            sysFile.setDirTag(record.getDirTag());
            sysFile.setSize(record.getSize());
            sysFile.setUrl(record.getUrl());
            sysFile.setObjectName(record.getObjectName());
            sysFile.setContextType(record.getContentType());
            sysFile.setETag(record.getMd5Hash()); // 将MD5存储在eTag字段中
            
            // 设置审计字段
            sysFile.setCreateTime(java.time.LocalDateTime.now());
            try {
                Long userId = cn.dev33.satoken.stp.StpUtil.getLoginIdAsLong();
                sysFile.setCreateId(userId);
            } catch (Exception e) {
                // 如果获取登录用户失败，则不设置创建人
                log.debug("获取当前用户信息失败，跳过设置创建人: {}", e.getMessage());
            }
            
            // 保存到数据库
            sysFileService.save(sysFile);
            log.debug("文件信息已保存到 sys_file 表，文件ID: {}, MD5: {}", sysFile.getId(), record.getMd5Hash());
            return sysFile.getId();
        } catch (Exception e) {
            log.error("记录文件信息到数据库失败: {}", e.getMessage(), e);
            return null;
        }
    }

    /**
     * 获取文件Content-Type
     */
    private String getContentType(String filename) {
        String extension = filename.substring(filename.lastIndexOf('.') + 1).toLowerCase();
        switch (extension) {
            case "jpg":
            case "jpeg":
                return "image/jpeg";
            case "png":
                return "image/png";
            case "gif":
                return "image/gif";
            case "bmp":
                return "image/bmp";
            case "webp":
                return "image/webp";
            default:
                return "application/octet-stream";
        }
    }

    /**
     * 本地文件记录内部类
     */
    private static class LocalFileRecord {
        private final String localPath;
        private final String filename;
        private final long size;
        private final String contentType;
        private final String objectName;
        private final String url;
        private final String md5Hash;

        public LocalFileRecord(String localPath, String filename, long size, 
                              String contentType, String objectName, String url, String md5Hash) {
            this.localPath = localPath;
            this.filename = filename;
            this.size = size;
            this.contentType = contentType;
            this.objectName = objectName;
            this.url = url;
            this.md5Hash = md5Hash;
        }

        public String getLocalPath() { return localPath; }
        public String getFilename() { return filename; }
        public long getSize() { return size; }
        public String getContentType() { return contentType; }
        public String getObjectName() { return objectName; }
        public String getUrl() { return url; }
        public String getMd5Hash() { return md5Hash; }
        public String getDirTag() { 
            // 从 objectName 中提取目录部分
            int lastSlash = objectName.lastIndexOf('/');
            return lastSlash > 0 ? objectName.substring(0, lastSlash) : "";
        }
    }

    /**
     * 记录当前监控状态（用于调试）
     */
    public void logMonitoringState() {
        log.info("===== 当前监控状态 =====");
        log.info("正在运行的监控任务数量: {}", watchServices.size());
        for (Long monitorId : watchServices.keySet()) {
            log.info("监控ID: {}", monitorId);
        }
        log.info("已注册的目录数量: {}", watchKeyToPath.size());
        for (Map.Entry<WatchKey, Path> entry : watchKeyToPath.entrySet()) {
            log.info("监控目录: {} (WatchKey有效: {})", 
                    entry.getValue(), entry.getKey().isValid());
        }
        log.info("============================");
    }

}